package cz.spock.core.service;

import java.util.ArrayList;
import java.util.List;

import org.apache.commons.validator.EmailValidator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.encoding.ShaPasswordEncoder;
import org.springframework.stereotype.Service;

import com.google.appengine.api.datastore.Email;
import com.googlecode.objectify.Key;

import cz.spock.core.entity.Account;
import cz.spock.core.entity.security.AccountSecurity;
import cz.spock.core.entity.security.SpockGrantedAuthority;
import cz.spock.exception.DatabaseException;
import cz.spock.exception.InvalidDataException;
import cz.spock.exception.InvalidInputException;

/**
 * service class for Account and AccountSecurity entities
 * 
 * @author miso
 *
 */
@Service
public class AccountService extends BaseService {
    
    @Autowired
    private ShaPasswordEncoder passwordEncoder;
    
    
    /**
     * receives account values
     * validates values
     * creates and persists account
     * returns account object with assigned id
     * 
     * throws invalid input exception when input data are wrong
     * throws invalid data exception when input data can not be used (for example not unique)
     * 
     * @param login
     * @param email
     * @return
     */
    public Account createAccount(String login, String email) {
        // validate data
        if(login == null || login.isEmpty()) {
            log.fine("login is null or empty");            
            throw new InvalidInputException("Login is required.", 411);
        }
        if(email == null) {
            log.fine("email is null");            
            throw new InvalidInputException("Email is required.", 412);
        }
        email = email.trim();
        if (!EmailValidator.getInstance().isValid(email)) {
            log.fine("email (" +email+ ") is not valid");
            throw new InvalidInputException("Email is not valid.", 4121);
        }
        if (!this.isEmailUnique(new Email(email))) {
            log.fine("email (" +email+ ") is already used");
            throw new InvalidDataException("Email ("+email+") is already used.", 421);
        }
        if(!this.isLoginUnique(login)) {
            log.fine("login (" +login+ ") is already used");
            throw new InvalidDataException("Login ("+login+") is already used.", 422);
        }
        // create account
        Account acc = new Account();
        acc.setLogin(login);
        acc.setEmail(new Email(email));
        // persist account with control
        Key<Account> k = dao.ofy().put(acc);
        acc = null;
        acc = dao.ofy().get(k);
        if(acc == null) {
            log.fine("Fatal database error in createAccount()");
            throw new DatabaseException("Database error, persisting of new account failed.");
        }
        return acc;
    }
    
    /**
     * receives account security values
     * validates values
     * creates and persists account
     * returns account object with assigned id  
     * 
     * @param login
     * @param password
     * @param roles
     * @param valid
     * @return
     */
    // TODO add salt
    public AccountSecurity createAccountSecurity(String login, String password, List<String> roles, boolean valid) {
        if(login == null) {
            log.fine("login is null");            
            throw new InvalidInputException("Login is required.", 411);
        }
        if(password == null) {            
            log.fine("password is null");            
            throw new InvalidInputException("Password is required.", 412);
        }        
        if(!this.isLoginUniqueAS(login)) {
            log.fine("login (" +login+ ") is already used");
            throw new InvalidDataException("Login ("+login+") is already used.", 422);
        }
        // adding default role if no roles set
        if(roles == null) {
            roles = new ArrayList<String>();
            roles.add("ROLE_USER");
        }
        List<Key<SpockGrantedAuthority>> grantedRoles = new ArrayList<Key<SpockGrantedAuthority>>();
        for(String s:roles) {
            grantedRoles.add(dao.fact().<SpockGrantedAuthority>getKey(this.createRole(s)));            
        }
        
        AccountSecurity accsec = new AccountSecurity();
        accsec.setLogin(login);
        accsec.setPassword(passwordEncoder.encodePassword(password, null));
        accsec.setValid(valid);
        accsec.setGrantedAuthorities(grantedRoles);
        Key<AccountSecurity> k = dao.ofy().put(accsec);
        accsec = null;
        accsec = dao.ofy().get(k);
        if(accsec == null) {
            log.fine("Fatal database error in createAccount()");
            throw new DatabaseException("Database error, persisting of new account failed.");
        }
        return accsec;
    }
    
    /**
     * creates new role if doesn't exist
     * 
     * *role is GrantedAuthority object for spring security
     * 
     * @param role
     * @return role - just created role or found existing role
     */
    public SpockGrantedAuthority createRole(String role) {
        if(role == null) {
            log.fine("role is null");            
            throw new InvalidInputException("Role is required.", 411);
        }
        SpockGrantedAuthority r = dao.ofy().query(SpockGrantedAuthority.class).filter("role", role).get(); 
        if(r != null) {
            return r;
        } else {
            r = new SpockGrantedAuthority();
            r.setRole(role);
            Key<SpockGrantedAuthority> k = dao.ofy().put(r);            
            r = null;
            r = dao.ofy().get(k);
            if(r == null) {
                log.fine("Fatal database error in createRole()");
                throw new DatabaseException("Database error, persisting of new role failed.");
            }
            return r;
        }        
    }
    
    
    public Account findAccountByLogin(String login) {
        if(login == null) {
            return null;
        }
        return dao.ofy().query(Account.class).filter("login", login).get();
    }
    
    /**
     * searches whether email is already used (is in database of accounts)
     * 
     * @param email
     * @return
     */
    public boolean isEmailUnique(Email email) {
        if (dao.ofy().query(Account.class).filter("email", email).get() == null)
            return true;
        return false;
    }
    
    /**
     * searches whether login is unique
     * 
     * @param login
     * @return
     */
    public boolean isLoginUnique(String login) {
        if(dao.ofy().query(Account.class).filter("login", login).get() == null)
            return true;
        return false;
    }
    
    /**
     * searches whether login is unique in account security
     * 
     * @param login
     * @return
     */
    public boolean isLoginUniqueAS(String login) {
        if(dao.ofy().query(AccountSecurity.class).filter("login", login).get() == null)
            return true;
        return false;
    }

}
